/*
 * Copyright (C) 2008-2010 Oliver Horn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.funcj.data;

import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterators;
import com.googlecode.funcj.Transformable;
import com.googlecode.funcj.data.Either.Left;
import com.googlecode.funcj.data.Either.Right;
import com.googlecode.funcj.data.Maybe.Nothing;

/**
 * An optional value.
 *
 * A {@code Maybe} object is either a {@link Just} object (which encapsulates a value) or
 * a {@link Nothing} object (which is empty).
 *
 * @param T value type
 *
 * @see Maybes
 *
 * @author Oliver Horn
 */
@Immutable
public abstract class Maybe<T> implements Serializable, Iterable<T>, Supplier<T>, Transformable<T> {

	private static final long serialVersionUID = 5662322395757918646L;

	private Maybe() {
	}

	/**
	 * Returns whether this object is a {@link Just} object.
	 * @return {@code true} if this object is a {@link Just} object, {@code} false otherwise
	 */
	public boolean isJust() {
		return false;
	}

	/**
	 * Returns whether this object is {@link Nothing}.
	 * @return {@code true} if this object is {@link Nothing}, {@code} false otherwise
	 */
	public boolean isNothing() {
		return false;
	}

	/**
	 * Extracts the value of a {@link Just} object or throws a {@link NothingException} exception.
	 * @return the value encapsulated by {@link Just}
	 * @throws NothingException if this object is {@link Nothing}
	 */
	public abstract T get() throws NothingException;

    /**
     * Extracts the value of a {@link Just} object or returns the specified default value.
     * @param defaultValue the default value for the {@link Nothing} case
     * @return the value encapsulated by {@link Just} or the default value
     */
	public abstract T getOrElse(@Nullable T defaultValue);

    /**
     * Extracts the value of a {@link Just} object or returns the value
     * of specified {@link Supplier}.
     * @param defaultSupplier the {@link Supplier} used for the {@link Nothing} case
     * @return the value encapsulated by {@link Just} or the value of the {@link Supplier}
     */
	public abstract T getOrElse(Supplier<T> defaultSupplier);

	public final <V> V maybe(Map<? super T, ? extends V> map, @Nullable V defaultValue) {
		return maybe(Functions.forMap(map, defaultValue), defaultValue);
	}

	public abstract <V> V maybe(Function<? super T, ? extends V> function, @Nullable V defaultValue);

	public abstract <V> V maybe(Function<? super T, ? extends V> function, Supplier<V> defaultValue);

	public abstract <B> Maybe<B> transform(Function<? super T, B> function);

	public abstract <L> Either<L, T> toEither(@Nullable L leftValue);

	/**
	 * Returns an immutable {@link Set} representation of this object. In case of {@link Just} the
	 * set is a singleton, in case of {@link Nothing} the set is the empty set.
	 * @return the {@link Set} representation of this object.
	 */
	public abstract Set<T> toSet();

	/**
	 * Returns an iterator for this object. In case of {@link Just} the iterator is a singleton,
	 * in case of {@link Nothing} the iterator is empty.
	 * @return the iterator
	 * @see #toSet()
	 */
	public abstract Iterator<T> iterator();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public abstract int hashCode();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public abstract boolean equals(Object obj);

	/**
	 * {@inheritDoc}
	 */
	@Override
	public abstract String toString();

	/**
	 * Unchecked exception thrown by {@code Nothing} to indicate that it does not contain a value.
     *
     * @see Maybe
     * @see Nothing
	 */
	public static class NothingException extends NoSuchElementException {

        private static final long serialVersionUID = -8352185629296004196L;

        /**
         * Constructs a {@code NothingException}.
         */
        public NothingException() {
        }
	}

	/**
	 * An empty {@code Maybe} object.
     *
     * @see Maybe
	 */
	@Immutable
	public static final class Nothing<T> extends Maybe<T> {

		private static final long serialVersionUID = -7179806854306570299L;

		/**
		 * Constructs a new empty {@code Maybe} object.
		 */
		public Nothing() {
		}

		@Override
		public boolean isNothing() {
			return true;
		}

		@Override
		public T get() {
			throw new NothingException();
		}

		@Override
		public T getOrElse(T defaultValue) {
			return defaultValue;
		}

		@Override
		public T getOrElse(Supplier<T> defaultSupplier) {
			return defaultSupplier.get();
		}

		@Override
		public Iterator<T> iterator() {
			return Iterators.emptyIterator();
		}

		@Override
		public <V> Nothing<V> transform(Function<? super T, V> function) {
			return Maybes.nothing();
		}

		@Override
		public <V> V maybe(Function<? super T, ? extends V> function, V defaultValue) {
			return defaultValue;
		}

		@Override
		public <V> V maybe(Function<? super T, ? extends V> function, Supplier<V> defaultSupplier) {
			return defaultSupplier.get();
		}

		@Override
		public <L> Left<L, T> toEither(L leftValue) {
			return Eithers.left(leftValue);
		}

		@Override
		public Set<T> toSet() {
			return Collections.emptySet();
		}

		@Override
		public int hashCode() {
			return 0;
		}

		@Override
		public boolean equals(Object obj) {
			return (obj instanceof Nothing);
		}

		@Override
		public String toString() {
			return "Maybe.Nothing";
		}
	}

    /**
     * A {@code Maybe} object that encapsulates a value.
     *
     * @see Maybe
     */
	@Immutable
	public static final class Just<T> extends Maybe<T> {

		private static final long serialVersionUID = 1181767264062866635L;

		private final T value;

		/**
		 * Constructs a new {@code Maybe} object with the given value.
		 * @param value the value of this {@code Maybe}
		 */
		// TODO: @ConstructorProperties("value")
		public Just(@Nullable T value) {
			this.value = value;
		}

		/**
		 * Returns the value of this {@code Maybe} object.
		 * @return value the value of this {@code Maybe}
		 */
		public T getValue() {
			return value;
		}

		@Override
		public boolean isJust() {
			return true;
		}

		@Override
		public T get() {
			return value;
		}

		@Override
		public T getOrElse(T defaultValue) {
			return value;
		}

		@Override
		public T getOrElse(Supplier<T> defaultSupplier) {
			return value;
		}

		@Override
		public Iterator<T> iterator() {
			return Iterators.singletonIterator(value);
		}

		@Override
		public <V> Just<V> transform(Function<? super T, V> function) {
			return new Just<V>(function.apply(value));
		}

		@Override
		public <L> Right<L,T> toEither(L leftValue) {
			return Eithers.right(value);
		}

		@Override
		public Set<T> toSet() {
			return Collections.singleton(value);
		}

		@Override
		public <V> V maybe(Function<? super T, ? extends V> function, V defaultValue) {
			return function.apply(value);
		}

		@Override
		public <V> V maybe(Function<? super T, ? extends V> function, Supplier<V> defaultSupplier) {
			return function.apply(value);
		}

		@Override
		public int hashCode() {
			return (value != null) ? value.hashCode() : 0;
		}

		@Override
		public boolean equals(Object obj) {
			return (obj instanceof Just) && Objects.equal(value, ((Just<?>) obj).value);
		}

		@Override
		public String toString() {
			return "Maybe.Just[" + value + "]";
		}
	}
}
